package com.swak.wechat;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

import com.swak.codec.Encodes;
import com.swak.http.HttpService;
import com.swak.incrementer.UUIdGenerator;
import com.swak.utils.JsonMapper;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;
import com.swak.wechat.codec.SignUtils;
import com.swak.wechat.sem.Qrcode;
import com.swak.wechat.sem.QrcodeResult;
import com.swak.wechat.tmpmsg.TemplateMessageResult;
import com.swak.wechat.token.AccessToken;
import com.swak.wechat.token.Ticket;
import com.swak.wechat.user.SnsToken;
import com.swak.wechat.user.UserInfo;

/**
 * Wechat 服务
 *
 * @author: lifeng
 * @date: 2020/4/1 11:38
 */
public abstract class AbstractWechatService {

	/**
	 * Http 服务
	 *
	 * @return http 服务
	 */
	public abstract HttpService getHttpService();

	// ==========================================================
	// 调用 API: userinfo
	// ==========================================================

	/**
	 * 获取用户信息
	 *
	 * @param accessToken 接入token
	 * @param openId      用户 openId
	 * @return 异步结果
	 */
	public CompletableFuture<UserInfo> userinfo(String accessToken, String openId) {
		CompletableFuture<UserInfo> future = this.getHttpService().post()
				.setUrl("https://api.weixin.qq.com/cgi-bin/user/info").addFormParam("access_token", accessToken)
				.addFormParam("openid", openId).addFormParam("lang", "zh_CN").jsonResult(UserInfo.class).future();
		return future.thenApply(res -> res);
	}

	/**
	 * 发送模板消息
	 *
	 * @param accessToken 接入token
	 * @param messageJson 模板消息
	 * @return 异步结果
	 */
	public CompletableFuture<TemplateMessageResult> sendTemplateMessage(String accessToken, String messageJson) {
		CompletableFuture<TemplateMessageResult> future = this.getHttpService().post()
				.setUrl("https://api.weixin.qq.com/cgi-bin/message/template/send")
				.addQueryParam("access_token", accessToken).setBody(StringUtils.getBytesUtf8(messageJson))
				.jsonResult(TemplateMessageResult.class).future();
		return future.thenApply(res -> res);
	}

	// ==========================================================
	// 网页授权登录 toAuthorize -> oauth2AccessToken -> accessToken2Userinfo
	// ==========================================================

	/**
	 * 授权登录的地址
	 *
	 * @param app   app
	 * @param url   地址
	 * @param state state
	 * @return 异步结果
	 */
	public CompletableFuture<String> toAuthorize(String appId, String url, String state) {
		String content = com.swak.Constants.REDIRECT_URL_PREFIX + "https://open.weixin.qq.com/connect/oauth2/authorize"
				+ "?appid=" + appId + "&redirect_uri=" + Encodes.urlEncode(url) + "&response_type=code" + "&scope="
				+ "snsapi_userinfo" + "&state=" + state + "#wechat_redirect";
		return CompletableFuture.completedFuture(content);
	}

	/**
	 * 公众号：换取token
	 *
	 * @param app   app
	 * @param code  code
	 * @param state state
	 * @return 异步结果
	 */
	public CompletableFuture<SnsToken> oauth2AccessToken(WechatConfig app, String code, String state) {
		if (!(StringUtils.isNotBlank(code) && StringUtils.isNotBlank(state))) {
			return CompletableFuture.completedFuture(null);
		}
		CompletableFuture<SnsToken> future = this.getHttpService().get()
				.setUrl("https://api.weixin.qq.com/sns/oauth2/access_token").addQueryParam("appid", app.getAppId())
				.addQueryParam("secret", app.getSecret()).addQueryParam("code", code)
				.addQueryParam("grant_type", "authorization_code").jsonResult(SnsToken.class).future();
		return future.thenApply(res -> {
			if (res != null) {
				res.setAppId(app.getAppId());
				res.setCode(code);
				res.setState(state);
			}
			return res;
		});
	}

	/**
	 * 换取user
	 *
	 * @param token token
	 * @return 异步结果
	 */
	public CompletableFuture<UserInfo> accessToken2Userinfo(SnsToken token) {
		if (token == null || StringUtils.isBlank(token.getAccess_token())) {
			return CompletableFuture.completedFuture(null);
		}
		CompletableFuture<UserInfo> future = this.getHttpService().get()
				.setUrl("https://api.weixin.qq.com/sns/userinfo").addQueryParam("access_token", token.getAccess_token())
				.addQueryParam("openid", token.getOpenid()).addQueryParam("lang", "zh_CN").jsonResult(UserInfo.class)
				.future();
		return future.thenApply(res -> {
			if (res != null) {
				res.setToken(token);
			}
			return res;
		});
	}

	// ==========================================================
	// 调用 API 的token
	// ==========================================================

	/**
	 * 获取接入系统的 accessToken
	 *
	 * @param app app
	 * @return 异步结果
	 */
	public CompletableFuture<AccessToken> refreshAccessToken(WechatConfig app) {
		CompletableFuture<AccessToken> future = this.getHttpService().get()
				.setUrl("https://api.weixin.qq.com/cgi-bin/token").addQueryParam("grant_type", "client_credential")
				.addQueryParam("appid", app.getAppId()).addQueryParam("secret", app.getSecret())
				.jsonResult(AccessToken.class).future();
		return future.thenApply(token -> {
			if (token != null) {
				token.setAddTime(System.currentTimeMillis());
			}
			return token;
		});
	}

	// ==========================================================
	// URL 签名
	// ==========================================================

	/**
	 * URL 签名的 Ticket
	 *
	 * @param token token
	 * @return 异步结果
	 */
	public CompletableFuture<Ticket> refreshTicket(WechatConfig app) {
		CompletableFuture<Ticket> future = this.getHttpService().get()
				.setUrl("https://api.weixin.qq.com/cgi-bin/ticket/getticket")
				.addQueryParam("access_token", app.getAccessToken()).addQueryParam("type", "jsapi")
				.jsonResult(Ticket.class).future();
		return future.thenApply(ticket -> {
			if (ticket != null) {
				ticket.setAddTime(System.currentTimeMillis());
			}
			return ticket;
		});
	}

	/**
	 * 对 URL 进行签名
	 *
	 * @param app    app
	 * @param ticket 票据
	 * @param url    地址
	 * @return 签名数据
	 */
	public Map<String, String> signUrl(WechatConfig app, Ticket ticket, String url) {
		String noncestr = UUIdGenerator.uuid();
		String timestamp = Long.toString(System.currentTimeMillis() / 1000);

		// 生成签名
		String signStr = "jsapi_ticket=" + ticket.getTicket() + "&" + "noncestr=" + noncestr + "&" + "timestamp="
				+ timestamp + "&" + "url=" + url;
		String signature = SignUtils.urlSign(signStr);
		Map<String, String> reMap = Maps.newHashMap();
		reMap.put("noncestr", noncestr);
		reMap.put("timestamp", timestamp);
		reMap.put("signature", signature.toLowerCase());
		reMap.put("url", url);
		reMap.put("appId", app.getAppId());
		return reMap;
	}

	// ==========================================================
	// 获取二维码
	// ==========================================================
	/**
	 * 生成二维码
	 *
	 * @param qrcode 生成二维码的参数
	 * @return 异步结果
	 */
	public CompletableFuture<QrcodeResult> createAppScanQrcode(WechatConfig app, Qrcode qrcode) {
		CompletableFuture<QrcodeResult> future = this.getHttpService().postJson()
				.setUrl("https://api.weixin.qq.com/cgi-bin/qrcode/create")
				.addQueryParam("access_token", app.getAccessToken())
				.setBody(StringUtils.getBytesUtf8(JsonMapper.toJson(qrcode))).jsonResult(QrcodeResult.class).future();
		return future.thenApply(ticket -> {
			return ticket;
		});
	}

	// ==========================================================
	// 接入微信: 接入验证 -》 事件处理
	// ==========================================================

	/**
	 * 接入签名
	 *
	 * @param app       app
	 * @param timestamp 签名时间
	 * @param nonce     随机字符串
	 * @param signature 签名
	 * @return 接入签名
	 */
	public boolean accessSign(WechatConfig app, String timestamp, String nonce, String signature) {
		return SignUtils.accessSign(app.getToken(), timestamp, nonce).equals(signature);
	}
}
